import numpy as np
from utils.features import prepare_for_training


class LinearRegression:
    # 构造函数，对数据进行预处理
    def __init__(self, labels, data, polynomial_degree=0, sinusoid_degree=0, normalize_data=True):
        # 调用utils.features.prepare_for_training函数对数据进行预处理
        # data为输入数据，polynomial_degree为多项式的阶数，sinusoid_degree为正弦函数的阶数，normalize_data为是否对数据进行归一化
        (self.data, self.features_mean, self.features_std) = prepare_for_training(data, polynomial_degree,
                                                                                  sinusoid_degree, normalize_data)
        # 将标签值放入labels属性中
        self.labels = labels
        self.polynomial_degree = polynomial_degree
        self.sinusoid_degree = sinusoid_degree
        self.normalize_data = normalize_data
        # 定义属性theta，用于存储线性回归的参数，初始化为一个m*1的零向量
        self.theta = np.ones((self.data.shape[1], 1))

    # 定义训练train函数，用于训练线性回归模型，传入参数有alpha和num_iterations
    def train(self, alpha, num_iterations=500):
        # 调用梯度下降算法，训练线性回归模型
        cost_history = self.gradient_descent(alpha, num_iterations)
        return cost_history

    # 定义梯度下降算法，用于训练线性回归模型
    def gradient_descent(self, alpha, num_iterations):
        cost_history = []
        # 定义循环，用于迭代训练
        for _ in range(num_iterations):
            # 调用参数更新函数，更新theta
            self.gradient_step(alpha)
            # 计算当前损失值
            cost = self.compute_cost(self.data, self.labels, self.theta)
            # 将损失值放入cost_history列表中
            cost_history.append(cost)
        return cost_history

    # 定义参数更新函数，用于每一次迭代更新参数,即theta
    def gradient_step(self, alpha):
        # 计算所有的样本总数
        num_examples = self.data.shape[0]
        # 计算预测值
        predictions = self.hypothesis(self.theta)
        # 计算新的theta(m*1)值,self.data.T为(m*n),predictions-self.labels为(n*1)
        self.theta = self.theta - alpha * (1.0 / num_examples) * np.dot(self.data.T, (predictions - self.labels))

    # 定义计算损失函数，用于计算当前模型的预测值与真实值之间的差距
    def compute_cost(self, data, labels, theta):
        # 计算样本总数
        num_examples = data.shape[0]
        # 计算预测值
        predictions = self.hypothesis(theta)
        # 计算平方的平均误差
        squared_errors = (1.0 / num_examples) * np.dot((predictions - labels).T, (predictions - labels))
        # print("squared_errors:", squared_errors[0][0])
        return squared_errors[0][0]

    # 定义预测函数(静态方法)，用于预测新数据
    def hypothesis(self, theta):
        # 即data*theta(n*1)，self.data为输入数据(n*m)，theta为线性回归的参数(m*1)
        return np.dot(self.data, theta)

    # 得到预测值
    def get_predict(self, data):
        # 对输入数据进行预处理
        (self.data, _, _) = prepare_for_training(data, self.polynomial_degree, self.sinusoid_degree, self.normalize_data)
        # 调用预测函数，得到预测值
        return self.hypothesis(self.theta)

    # 得到损失值
    def get_cost(self):
        # 调用计算损失函数，得到损失值
        return self.compute_cost(self.data, self.labels, self.theta)
